﻿namespace Lite.Net
{
    using System;
    using System.Diagnostics;
    using Lite.Common;
    using System.Net.Sockets;

    internal class SocketMonitor
                : SocketMonitorBase, ISession
    {
        #region Private Data Members

        private readonly int defaultBufferSize = 1024;

        private IBufferHandle _receiveBufferHandle;

        private Socket _socket;

        private SocketManager _manager;

        #endregion

        #region Con/Destructors

        public SocketMonitor(SocketManager manager, Socket socket)
        {
            _manager = manager;

            _receiveBufferHandle = _manager.Pool.RequestBuffer(defaultBufferSize);

            _socket = socket;
        }

        #endregion

        #region Public Methods

        public void Close()
        {
            if (_socket != null && _socket.Connected)
                _socket.Shutdown(SocketShutdown.Both);

            Dispose();
        }

        public void Write(byte[] data)
        {
            if (_socket.Connected)
            {
                _socket.Send(data);
            }
            else
            {
                throw new InvalidOperationException("Session is not currently connected.");
            }
        }

        #endregion

        protected override void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                _receiveBufferHandle.Dispose();
            }

            base.Dispose(isDisposing);
        }

        protected override bool BeginAsyncOperation(SocketAsyncEventArgs eventArgs)
        {
            eventArgs.SetBuffer(_receiveBufferHandle.GetBuffer(), 0, _receiveBufferHandle.GetBuffer().Length);
            eventArgs.UserToken = this;
            return _socket.ReceiveAsync(eventArgs);
        }

        /// <summary>
        /// Copies the received data into a new buffer and dispatches it
        /// to be handled by the DataReceived event.
        /// </summary>
        /// <returns>
        /// True if the connection is still open and polling should
        /// continue, otherwise false.
        /// </returns>
        protected override bool CompleteAsyncOperation(SocketAsyncEventArgs eventArgs)
        {
            int bytesReceived = eventArgs.BytesTransferred;

            if (bytesReceived > 0)
            {
                Debug.Print("{0} bytes received.", bytesReceived);
                IBufferHandle dataBuffer = _manager.Pool.RequestBuffer(bytesReceived);
                Array.ConstrainedCopy(eventArgs.Buffer, 0, dataBuffer.GetBuffer(), 0, bytesReceived);

                OnDataReceived(dataBuffer, bytesReceived);

                return true;
            }
            else
            {
                OnConnectionDropped();
                return false;
            }
        }

        private void OnDataReceived(IBufferHandle data, int bytesReceived)
        {
            EventHandler<DataBufferEventArgs> dataReceived = DataReceived;

            if (dataReceived != null)
            {
                dataReceived(this, new DataBufferEventArgs(data, bytesReceived));
            }
        }

        protected void OnConnectionDropped()
        {
            var connectionDropped = ConnectionDropped;

            if (connectionDropped != null)
                connectionDropped.InvokeAsync(this, EventArgs.Empty);
        }

        #region Events

        public event EventHandler ConnectionDropped;

        public event EventHandler<DataBufferEventArgs> DataReceived;

        #endregion
    }
}